<?php
// +----------------------------------------------------------------------
// | INPHP
// | Copyright (c) 2023 https://inphp.cc All rights reserved.
// | Licensed ( https://opensource.org/licenses/MIT )
// | Author: 幺月儿(https://gitee.com/lulanyin) Email: inphp@qq.com
// +----------------------------------------------------------------------
// | http response 对象
// +----------------------------------------------------------------------
namespace Inphp\Core\Services\Http;

use Inphp\Core\Config;
use Inphp\Core\Context;
use Inphp\Core\Middlewares;
use Inphp\Core\Object\Message;
use Inphp\Core\Object\RouterStatus;
use Inphp\Core\Service;
use Inphp\Core\Util\Str;

class Response
{
    /** 响应内容类型 **/
    const CONTENT_TYPES = [self::CONTENT_TYPE_HTML, self::CONTENT_TYPE_JSON, self::CONTENT_TYPE_TEXT, self::CONTENT_TYPE_TEXT_XML, self::CONTENT_TYPE_APPLICATION_OCTET_STREAM, self::CONTENT_TYPE_APPLICATION_XML, self::CONTENT_TYPE_IMAGE_GIF, self::CONTENT_TYPE_IMAGE_JPEG, self::CONTENT_TYPE_IMAGE_PNG];
    //text
    const CONTENT_TYPE_HTML                     = "text/html";
    const CONTENT_TYPE_TEXT_HTML                = "text/html";
    const CONTENT_TYPE_TEXT                     = "text/plant";
    const CONTENT_TYPE_TEXT_PLANT               = "text/plant";
    const CONTENT_TYPE_TEXT_XML                 = "text/xml";
    //application
    const CONTENT_TYPE_JSON                     = "application/json";
    const CONTENT_TYPE_APPLICATION_JSON         = "application/json";
    const CONTENT_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream";
    const CONTENT_TYPE_APPLICATION_XML          = "application/xml";
    //image
    const CONTENT_TYPE_IMAGE_GIF                = "image/gif";
    const CONTENT_TYPE_IMAGE_JPEG               = "image/jpeg";
    const CONTENT_TYPE_IMAGE_PNG                = "image/png";

    /**
     * 对象
     * @var Server
     */
    private Server $server;

    /**
     * swoole\http\request 对象
     * 此对象仅在CLI运行时才会有效
     * @var \Swoole\Http\Request
     */
    private \Swoole\Http\Request $swooleHttpRequest;

    /**
     * swoole\http\response 对象
     * 此对象仅在CLI运行时才会有效
     * @var \Swoole\Http\Response
     */
    private \Swoole\Http\Response $swooleHttpResponse;

    /**
     * 设置路由状态
     * @var RouterStatus
     */
    private RouterStatus $routerStatus;

    /**
     * 临时数据
     * @var mixed
     */
    public mixed $data = "";

    /**
     * 响应 headers 数据
     * @var array
     */
    private array $headers = [];

    /**
     * 响应的 body 数据
     * 数据可以是很多种类型，但是内置的处理实现是有限的
     * @var mixed|string
     */
    private mixed $body = "";

    /**
     * 是否已经完成响应
     * @var bool
     */
    public bool $end = false;

    public function __construct(int $status = 200)
    {
        //

    }

    /**
     * 设置 Server 对象
     * @param Server $server
     * @return Response
     */
    public function setServer(Server $server): Response
    {
        $this->server = $server;
        return $this;
    }

    /**
     * 获取 Server 对象
     * @return Server
     */
    public function getServer(): Server
    {
        return $this->server;
    }

    /**
     * 设置 \Swoole\Http\Request 对象
     * @param \Swoole\Http\Request $request
     * @return Response
     */
    public function setSwooleHttpRequest(\Swoole\Http\Request $request): Response
    {
        $this->swooleHttpRequest = $request;
        return $this;
    }

    /**
     * 获取 \Swoole\Http\Request 对象
     * @return \Swoole\Http\Request
     */
    public function getSwooleHttpRequest(): \Swoole\Http\Request
    {
        return $this->swooleHttpRequest;
    }

    /**
     * 设置 \Swoole\Http\Response 对象
     * @param \Swoole\Http\Response $response
     * @return Response
     */
    public function setSwooleHttpResponse(\Swoole\Http\Response $response): Response
    {
        $this->swooleHttpResponse = $response;
        return $this;
    }

    /**
     * 获取 \Swoole\Http\Response 对象
     * @return \Swoole\Http\Response
     */
    public function getSwooleHttpResponse(): \Swoole\Http\Response
    {
        return $this->swooleHttpResponse;
    }

    /**
     * @param RouterStatus $routerStatus
     * @return Response
     */
    public function setRouterStatus(RouterStatus $routerStatus): Response
    {
        $this->routerStatus = $routerStatus;
        return $this;
    }

    /**
     * 设置状态码
     * @param int $code
     * @return Response
     */
    public function withStatus(int $code): Response
    {
        $this->getRouterStatus()->status = $code;
        return $this;
    }

    /**
     * 获取路由状态
     * @return RouterStatus
     */
    public function getRouterStatus(): RouterStatus
    {
        return $this->routerStatus;
    }

    /**
     * 响应JSON
     * @param array|Message $array
     * @return Response
     */
    public function withJson(array|Message $array): Response
    {
        //如果属于100~599间的错误代码
        $error = is_array($array) ? ($array["error"] ?? 0) : $array->error;
        if ($error >= 100 && $error <= 599) {
            $this->routerStatus->status = $error;
        }
        return $this->withBody($array instanceof Message ? $array->toJson() : json_encode($array, 256), "application/json");
    }

    /**
     * 响应HTML
     * @param string $html
     * @return Response
     */
    public function withHtml(string $html): Response
    {
        return $this->withBody($html, "text/html");
    }

    /**
     * 响应普通文本内容
     * @param string $text
     * @return Response
     */
    public function withText(string $text): Response
    {
        return $this->withBody($text, "text/plant");
    }

    /**
     * 响应 BODY
     * @param mixed $body
     * @param string|null $contentType
     * @return Response
     */
    public function withBody(mixed $body, ?string $contentType = null): Response
    {
        $this->body = $body;
        if (!empty($contentType)) {
            $this->withContentType($contentType);
        }
        return $this;
    }

    /**
     * 获取 Body
     * @return mixed
     */
    public function getBody(): mixed
    {
        return $this->body ?? "";
    }

    /**
     * 响应内容类型声明
     * @param string $contentType
     * @return Response
     */
    public function withContentType(string $contentType): Response
    {
        $this->routerStatus->responseContentType = $contentType;
        return $this;
    }

    /**
     * 设置 header 某个值
     * @param string $name
     * @param string|array $value
     * @return Response
     */
    public function withHeader(string $name, string|array $value): Response
    {
        $this->headers[$name] = is_array($value) ? $value : [$value];
        return $this;
    }

    /**
     * 给 header 添加某个值
     * @param string $name
     * @param string|array $value
     * @return Response
     */
    public function withAddHeader(string $name, string|array $value): Response
    {
        $header = $this->headers[$name] ?? [];
        $values = is_array($value) ? $value : [$value];
        $values = array_merge($header, $values);
        $this->headers[$name] = array_unique($values);
        return $this;
    }

    /**
     * 处理跨域使用的，允许来源
     * @param string $value
     * @return Response
     */
    public function withAccessControlAllowOrigin(string $value): Response
    {
        return $this->withHeader("Access-Control-Allow-Origin", $value);
    }

    /**
     * 允许的 header 请求
     * @param string|array $value
     * @return Response
     */
    public function withAccessControlAllowHeaders(string|array $value): Response
    {
        return $this->withHeader("Access-Control-Allow-Headers", join(", ", $value));
    }

    /**
     * 允许的请求方式
     * @param string|array $value
     * @return Response
     */
    public function withAccessControlAllowMethods(string|array $value): Response
    {
        return $this->withHeader("Access-Control-Allow-Methods", join(", ", $value));
    }

    /**
     * 允许全部请求和跨域
     * @return Response
     */
    public function withAccessAll(): Response
    {
        return $this->withAccessControlAllowOrigin("*")
            ->withAccessControlAllowMethods("*")
            ->withAccessControlAllowHeaders("*");
    }

    /**
     * 获取所有设置的 header
     * @return array
     */
    public function getHeaders(): array
    {
        return $this->headers;
    }

    /**
     * 响应处理
     */
    public function ending(): void
    {
        if ($this->end) {
            return;
        }
        if ($this->routerStatus->status !== 200) {
            if ($this->routerStatus->responseContentType === self::CONTENT_TYPE_HTML) {
                Middlewares::process("showErrorPage", [$this->routerStatus->message]);
            }
            $this->error($this->routerStatus->status, $this->routerStatus->message, $this->routerStatus->responseContentType);
            return;
        }
        //如果是OPTIONS请求
        if (strtoupper($this->routerStatus->method) === "OPTIONS") {
            $this->endOptions();
            return;
        }
        //如果有控制器
        $controller = null;
        $controllerMethod = null;
        if (!is_null($this->routerStatus->controller) && (is_object($this->routerStatus->controller[0]) || class_exists($this->routerStatus->controller[0]))) {
            //执行方法
            $controllerMethod = $this->routerStatus->controller[1];
            //2023.11.9 CLI模式运行下，如果控制器已初始化过，需要销毁，再重新初始化
            if (is_object($this->routerStatus->controller[0])) {
                $controllerName = $this->routerStatus->controller[0]::class;
                unset($this->routerStatus->controller[0]);
            } else {
                $controllerName = $this->routerStatus->controller[0];
            }
            //注意，控制器，仅传一个参数
            $controller = new $controllerName($this);
            $this->routerStatus->controller[0] = $controller;
            $this->routerStatus->controller[1] = $controllerMethod;
            //执行前
            $this->processMiddleware("beforeExecute", [$controller, $controllerMethod]);
            if ($this->end) {
                return;
            }
        }
        //执行
        if (!is_null($controller)) {
            if ((empty($controllerMethod) || !method_exists($controller, $controllerMethod)) && !method_exists($controller, "__call")) {
                //未找到控制器的方法，无法执行
                $controllerMethod = $controllerMethod ?? "?";
                $controllerName = is_object($controller) ? $controller::class : $controller;
                $this->error(404, "未找到执行方法：{$controllerName}->{$controllerMethod}()", $this->routerStatus->responseContentType);
                return;
            }
            //可以处理注入的参数
            $result = $controller->{$controllerMethod}();
            if ($this->end) {
                return;
            }
            //如果响应数据类型是JSON
            if ((is_array($result) && $this->routerStatus->responseContentType === self::CONTENT_TYPE_JSON) || $result instanceof Message) {
                //这里返回一个固定的JSON对象
                $message = $result instanceof Message ? $result : (new Message())->data($result);
                $this->withJson($message->toArray());
                $this->routerStatus->responseContentType = self::CONTENT_TYPE_JSON;
                if ($message->error >= 100 && $message->error <= 599) {
                    $this->routerStatus->status = $message->error;
                }
            } elseif (!is_null($result)) {
                //如果不为空，将数据存到 data
                $this->data = $result;
            }
        }
        //响应前，处理中间件
        $this->processMiddleware("beforeEnd");
        //
        if ($this->end) {
            return;
        }
        //如果都没有内容响应
        if ($this->routerStatus->status === 200) {
            if (empty((string) $this->getBody())) {
                if ($this->routerStatus->responseContentType === self::CONTENT_TYPE_JSON) {
                    $this->withJson((new Message())->data($this->data)->toArray());
                } elseif ($this->routerStatus->responseContentType === self::CONTENT_TYPE_HTML) {
                    //从模板获取
                    $viewDir = $this->routerStatus->viewDir;
                    $viewFile = $this->routerStatus->view;
                    $view = $viewDir."/".$viewFile;
                    if (!empty($viewDir) && !empty($viewFile) && file_exists($view)) {
                        //允许执行PHP
                        $body = (function() use($view) : string {
                            //隔离变量
                            ob_start();
                            include $view;
                            $content = ob_get_contents();
                            ob_clean();
                            return $content;
                        })();
                        //
                        $this->withHtml($body);
                    }
                }
            }
        } else {
            $this->error($this->routerStatus->status, $this->routerStatus->message, $this->routerStatus->responseContentType);
        }
        //处理响应
        $this->end();
    }

    /**
     * 响应并结束
     */
    public function end(): void
    {
        if ($this->end) {
            return;
        }
        if ($this->routerStatus->status !== 200) {
            if ($this->routerStatus->responseContentType === self::CONTENT_TYPE_HTML) {
                Middlewares::process("showErrorPage", [$this->routerStatus->message]);
            }
            $this->error($this->routerStatus->status, $this->routerStatus->message, $this->routerStatus->responseContentType);
            return;
        }
        //如果是OPTIONS请求
        if (strtoupper($this->routerStatus->method) === "OPTIONS") {
            $this->endOptions();
            return;
        }
        //
        //客户端数据
        $client = Context::getClient();
        //处理cookie
        $cookies = [];
        foreach ($client->cookies as $cookieName => $cookieValue) {
            if (is_array($cookieValue)) {
                $cookies[$cookieName] = $cookieValue;
            }
        }
        if (!empty($cookies)) {
            //主域名配置
            $domains = Config::get("domain", []);
            //如果未定义，统一使用客户端请求的域名
            $domains["main"] = !empty($domains["main"]) ? $domains["main"] : $client->host;
            $cookieDomain = !empty($domains["cookie"]) ? $domains["cookie"] : $domains["main"];
            $cookieDomain = Str::deletePrefix($cookieDomain, ["http://", "https://"]);
            //设置cookie
            foreach ($cookies as $cookieName => $cookieValue) {
                if (Service::isCLI()) {
                    $this->swooleHttpResponse->cookie($cookieName, $cookieValue["value"] ?? null, $cookieValue["time"], "/", $cookieDomain, $client->https, true);
                } else {
                    setcookie($cookieName, $cookieValue["value"] ?? null, $cookieValue["time"], "/", $cookieDomain, $client->https, true);
                }
            }
        }
        //如果跨域，需要返回一些附加信息
        if ($client->cross) {
            $this->withAccessControlAllowHeaders($this->routerStatus->getAccessControlAllowHeaders())
                ->withAccessControlAllowMethods($this->routerStatus->getAccessControlAllowMethods())
                ->withAccessControlAllowOrigin($client->origin);
        }
        //编码和响应类型
        $this->withHeader("Content-Type", [$this->routerStatus->responseContentType, "charset=utf-8"]);
        //处理headers
        $headers = $this->getHeaders();
        foreach ($headers as $name => $header) {
            if (Service::isCLI()) {
                $this->swooleHttpResponse->header($name, implode("; ", $header));
            } else {
                header(join(":", [$name, implode("; ", $header)]), false, $this->routerStatus->status);
            }
        }
        //响应数据
        if (Service::isCLI()) {
            $this->swooleHttpResponse->status($this->routerStatus->status);
            $this->swooleHttpResponse->end((string) $this->getBody());
        } else {
            echo (string) $this->getBody();
        }
        //结束
        $this->end = true;
    }

    /**
     * 响应错误的消息
     * @param int $status
     * @param string $reason
     * @param string $contentType
     */
    public function error(int $status, string $reason = "", string $contentType = "text/plant"): void
    {
        if ($this->end) {
            return;
        }
        if ($contentType === self::CONTENT_TYPE_HTML) {
            Middlewares::process("showErrorPage", [$reason]);
            if ($this->end) {
                return;
            }
        }
        $devMode = defined("DEV_MODE") ? DEV_MODE : false;
        //响应数据
        if (Service::isCLI()) {
            $this->swooleHttpResponse->header("Content-Type", "{$contentType}; charset=utf-8");
            $this->swooleHttpResponse->status($status);
            $this->swooleHttpResponse->end($devMode ? $reason : '');
        } else {
            header("Content-Type: {$contentType}; charset=utf-8", true, $status);
            if ($devMode) {
                echo $reason;
            }
        }
        $this->end = true;
    }

    /**
     * 响应 OPTIONS 请求
     */
    private function endOptions(): void
    {
        $client = Context::getClient();
        //响应数据
        if (Service::isCLI()) {
            $this->swooleHttpResponse->header("Access-Control-Allow-Headers", join(",", $this->routerStatus->getAccessControlAllowHeaders()));
            $this->swooleHttpResponse->header("Access-Control-Allow-Method", join(",", $this->routerStatus->getAccessControlAllowMethods()));
            $this->swooleHttpResponse->header("Access-Control-Allow-Origin", $client->origin ?? "*");
            $this->swooleHttpResponse->status(200);
            $this->swooleHttpResponse->end();
        } else {
            header("Access-Control-Allow-Headers: ".join(",", $this->routerStatus->getAccessControlAllowHeaders()), true, 200);
            header("Access-Control-Allow-Method: ".join(",", $this->routerStatus->getAccessControlAllowMethods()));
            header("Access-Control-Allow-Origin: ".($client->origin ?? "*"));
        }
        $this->end = true;
    }

    /**
     * 重定向
     * @param string $url
     * @param int $code
     */
    public function redirect(string $url, int $code = 302): void
    {
        if (Service::isCLI()) {
            $this->swooleHttpResponse->redirect($url, $code);
            $this->end = true;
        } else {
            header("Location: {$url}", true, $code);
            exit;
        }
    }

    /**
     * 处理中间件
     * @param string $name
     * @param array $args
     */
    public function processMiddleware(string $name, array $args = []): void
    {
        Middlewares::process($name, array_merge([$this], $args));
    }
}